Skip to main content

JavaScript Notes

· 19 min read

prototype

  • Object.prototype
    • {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
  • Function.prototype
    • ƒ () { [native code] }
  • Foo.prototype (宣告 function Foo(){} 後)
    • {constructor: ƒ}

__proto__

所有 instance 的 __proto__其 internal prototype 例如:

  • new Object().__proto__
    • 是 Object.prototype
  • (function Foo(){}).__proto__
    • 是 Function.prototype
  • new Foo().__proto__
    • 是 Foo.prototype

prototype 的 __proto__

除了 Object prototype 的 __proto__null, 其他都是 Object.prototype

  • Object.prototype.__proto__
    • 是 null
  • Function.prototype.__proto__
    • 是 Object.prototype
  • Foo.prototype.__proto__
    • 是 Object.prototype

Function 的 __proto__

Function 的 __proto__ 都是 Function.prototype

  • Object.__proto__
    • 是 Function.prototype
  • Function.__proto__
    • 是 Function.prototype
  • Foo.__proto__
    • 是 Function.prototype

constructor

constructorprototype 互為 reversion


JS

https://mdn.mozillademos.org/zh-TW/docs/Learn/JavaScript/First_steps/Strings$samples/Hidden_code?revision=1654861

https://marketplace.visualstudio.com/items?itemName=Wscats.eno

<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>External JavaScript example</title>

<style></style>

<link rel="stylesheet" href="css/mystyle.css" />
<script src="js/script.js" defer></script>
</head>
<body>
<button>Click me</button>

<script></script>
</body>
</html>

script defer async

<script src="script.js" defer></script> 在這個例子中,腳本(JavaScript 程式)與 HTML 會同時載入,所以程式將正確地執行。 在外部程式的範例裡,我們不需要使用 DOMContentLoaded 事件因為 defer 為我們解決問題了。而在內部程式的範例裡我們沒用 defer 屬性,是因為 defer 屬性只能用於外部的腳本。 這個問題有另一個舊式的解決方法,就是將 script 元素放在 body 元素的底部(剛好在 </body> 的前面),如此它就會在所有 HTML 被解析完之後才被載入。這個方法的問題在於腳本的載入與解析工作會被完成擋住,一直到所有 HTML 載入完成。在擁有許多 JavaScript 的大型網站中,這樣會導致嚴重的效能問題,拖慢你的網站。 實際上,有兩個方法可以閃過腳本被擋到的問題:async 與 defer(前面看到的)。來看看兩者的區別。 async 和 defer 屬性都是用來告訴瀏覽器使用獨立線程(行程)來下載腳本,同一時間頁面的其它部分(如 DOM 元件之類)也在下載,因此頁面的載入不會因為腳本被影響。

  • 如果你的腳本應該立即被執行,而且不依賴其它腳本先被載入,就用 async 。
  • 如果你的腳本依賴其他腳本先被解析或 DOM 已經存在,就用 defer 來載入它們,並根據你想要瀏覽器執行的順序安排 <script> 元素的次序。
  • 有 async 或 defer 的 js 會與 html 同步下載
  • html -> async -> defer

Operators

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Expressions_and_Operators

a||b

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR if bool(a) is falsy, return b

a??b

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator if bool(a) is null or undefined, return b

a?b:c

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Conditional_Operator if bool(a) is truthy, return b, else return c

a.b

when a is null or undefined, throw error, else return a.b

a?.b

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Optional_chaining when a is null or undefined, return undefined, else return a.b


null 是一個表示“空”的對象,轉為數值時為 0;undefined 是一個表示"此處無定義"的原始值,轉為數值時為 NaN NaN null undefined Infinity

js 底層可是 253 ~= 1016, 所以 10**15 以內為精確 Number.MAX_VALUE // 1.7976931348623157e+308 Number.MIN_VALUE // 5e-324

不在 21024~2-1023 內會溢位

轉為 int32

function toInt32(x) {
return x | 0;
}

數字與字串轉換

let myNum = Number(myString);
let myString = myNum.toString();

手動轉換 type:

  • Number() or parseInt()
  • String()
  • Boolean()

小數取整數

return ~~f;
return f ^ 0;

bitwise operator SHIFT RIGHT

  • >>
  • >>> 頭部補零的右移運算符(>>>)與右移運算符(>>)只有一個差別,就是一個數的二進制形式向右移動時,頭部一律補零,而不考慮符號位。

設計開關控制

const FLAGS_0 = 1 << 0;
const FLAGS_1 = 1 << 1;
const FLAGS_2 = 1 << 2;
const FLAGS_3 = 1 << 3;

const FLAGS = {
開關0: FLAGS_0,
開關1: FLAGS_1,
開關2: FLAGS_2,
開關3: FLAGS_3,
};

var flag = FLAGS_1 | FLAGS_3; // 設定開關 1&3 打開

Object.keys(FLAGS).forEach((key) => {
if (flag & FLAGS[key]) {
console.log(`${key} is on. `);
}
});
// 開關1 is on.
// 開關3 is on.

右結合的運算

  • =
  • ?:
  • **

舉例

// 以下相同

w = x = y = z;
w = x = y = z;

q = a ? b : c ? d : e ? f : g;
q = a ? b : c ? d : e ? f : g;

2 ** (3 ** 2);
2 ** (3 ** 2);

兩整數交換

a ^= b, b ^= a, a ^= b;
let dog = {
name: "Spot",
breed: "Dalmatian",
};
console.log(dog.name); // Spot

字串

字串模板 Template literals

用 backtick characters (``)

常用 methods

string.length
string.indexOf('find') // 會回傳'find'首次出現在string的哪個index,沒找到會return -1
string.toLowerCase();
string.toUpperCase();
string.replace('moz','van');
string.repeat(2);
.includes()

str.slice(beginIndex[, endIndex])

.slice 可以把類似 Array 的object 轉成 Array

const str = "The quick brown fox jumps over the lazy dog.";
console.log(str.slice(31));
// expected output: "the lazy dog."
console.log(str.slice(4, 19));
// expected output: "quick brown fox"
console.log(str.slice(-4));
// expected output: "dog."
console.log(str.slice(-9, -5));
// expected output: "lazy"

多行字串

(function () {
/*
line 1
line 2
line 3
*/
}
.toString()
.split("\n")
.slice(1, -1)
.join("\n"));

反轉字串

revstr = Array.from(string).reverse().join("");
revstr = string.split("").reverse().join("");

array

string 轉換成 array

arr = Array.from(string);
arr.reverse(); // array 反轉

array 換 string

const elements = ["Fire", "Air", "Water"];
console.log(elements.join());
// expected output: "Fire,Air,Water"
console.log(elements.join(""));
// expected output: "FireAirWater"
console.log(elements.join("-"));
// expected output: "Fire-Air-Water"

array 是 object

var arr = [1, 2, 3];
console.log(typeof arr, arr, Object.keys(arr));
// object [ 1, 2, 3 ] [ '0', '1', '2' ]

arr["k"] = "v";
console.log(typeof arr, arr, Object.keys(arr));
// object [ 1, 2, 3, k: 'v' ] [ '0', '1', '2', 'k' ]

array 基本操作

var tmp;
var arr = ["0"];
console.log(arr);

tmp = arr.push("123"); // 加入最右
console.log(arr); // ["0", "123"]
console.log(tmp); // 2 (length)

tmp = arr.unshift("456", "789"); // 加入最左
console.log(arr); // ["456", "789", "0", "123"]
console.log(tmp); // 4 (length)

tmp = arr.pop(); // 移除最右
console.log(arr); // ["456", "789", "0"]
console.log(tmp); // 123

tmp = arr.shift(); // 移除最左
console.log(arr); // ["789", "0"]
console.log(tmp); // 456

將類 array 轉為 array

var a = [10, 20, 30];
console.log(a instanceof Array, a);
// true [ 10, 20, 30 ]

var arraylike = (function () {
return arguments;
})(10, 20, 30);
console.log(arraylike instanceof Array, arraylike);
// false [Arguments] { '0': 10, '1': 20, '2': 30 }

var arr;
arr = Array.prototype.slice.call(arraylike);
console.log(arr instanceof Array, arr);
// true [ 10, 20, 30 ]

搭配 for

var a = [10, 20, 30];
a.hi = "hi";

(() => {
let re = "";
for (let key in a) {
// 所有index
re += "(" + key + ", " + a[key] + "), ";
}
console.log(re);
})(); //(0, 10), (1, 20), (2, 30), (hi, hi),

(() => {
let re = "";
for (let i = 0; i < a.length; i++) {
//只有數字index
re += "(" + i + ", " + a[i] + "), ";
}
console.log(re);
})(); //(0, 10), (1, 20), (2, 30),

(() => {
let re = "";
a.forEach((e) => {
re += "(" + "" + ", " + e + "), ";
});
console.log(re);
})(); //(, 10), (, 20), (, 30),

foreach

Array.prototype.forEach.call(arr, func) 中的 func 最多可以傳入三個 arg,依序為:

  • index
  • 完整原傳入物件
function show_args(v, index, all) {
console.log(index + " : " + v, all);
}

var arrayLike = (function () {
return arguments;
})(10, 20, 30);
Array.prototype.forEach.call(arrayLike, show_args);
// 0 : 10 Arguments(3) [10, 20, 30, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 1 : 20 Arguments(3) [10, 20, 30, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 2 : 30 Arguments(3) [10, 20, 30, callee: ƒ, Symbol(Symbol.iterator): ƒ]

var o = [1, 2, 3];
Array.prototype.forEach.call(o, show_args);
// 0 : 1 (3) [1, 2, 3]
// 1 : 2 (3) [1, 2, 3]
// 2 : 3 (3) [1, 2, 3]

Object.entries()

const object1 = {
a: "somestring",
b: 42,
};

for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}

document.querySelector & document.querySelectorAll

選取方式

  • tag
  • .class
  • #id

處理第一筆

h2 的 tag

document.querySelector("h2").textContent = "123s";

處理全部

記下所有的 class="title"

var es = document.querySelectorAll(".title");

然後處理特定 index

es[0].textContent = "123";
es[1].textContent = "456a";
es[2].textContent = "789a";

或是放進迴圈一起處理

es.forEach((e) => {
e.textContent = "333";
});

屬性 attr

取得 attr

var get = el.getAttribute("href");

改變 attr

把屬性 href 改為 xx

el.setAttribute("href", "xx");

function

toString() 原始碼

function f(a, b) {}
console.log(f.toString()); //function f(a, b) {}

name()

var v02 = function f02(txt) {
console.log(txt);
};
console.log(v02.name); //f02

arguments[]

var f = function () {
console.log(arguments[0]);
console.log(arguments[1]);
};
f(0, 1, 2, 3);
//0
//1
var f = function (a, b) {
"use strict"; // 嚴格模式
arguments[0] = 3;
arguments[1] = 2;
return [a, b, arguments[0], arguments[1]];
};

console.log(f(1, 1));
//普通模式: [3, 2, 3, 2]
//嚴格模式: [1, 1, 3, 2]
var f = function f() {
let re = "";
for (let arg in arguments) {
re += arg + ", ";
}
console.log(re);
};
f(0, 1, 2, 3); //0, 1, 2, 3,

arguments 是 object , 可轉換成 array

var v = (function f() {
//1
var args1 = Array.prototype.slice.call(arguments);
//2
var args2 = [];
for (var i = 0; i < arguments.length; i++) {
args2.push(arguments[i]);
}
//output
console.log(args1, args2);
})(1, 2, 3); // [1,2,3] [1,2,3]

參數

length()

傳入的參數個數

function f(a, b) {}
f.length; // 2

參數傳遞方式

  • 參數是數值、字符串、布爾值: pass by value
  • 數組、對象、其他函數: pass by reference

修改參數 object 的屬性會影響, 因為 object 是 pass by reference

var obj = {
p: 1,
};

function f(o) {
o.p = 2;
return "->";
}
console.log(obj.p, f(obj), obj.p); //1 "->" 2

但是替換參數不影響

var obj = {
p: 1,
};

function f(o) {
o = {
p: "qwertyusdgdgh",
};
return "->";
}
console.log(obj, f(obj), obj); //{p: 1} "->" {p: 1}

參數傳遞可不按照宣告數量

function f(a, b) {
if (b) {
return b;
} else if (a) {
return a;
} else {
return 0;
}
}
console.log(f.length); // 2

console.log(f("a", "b", "c")); // b
console.log(f("d", "e")); // e
console.log(f("f")); // f
console.log(f()); // 0

函數不同寫法

  • function 可以提升
  • var
  • IIFE
f01("1. declaration 可以在宣告前呼叫");
function f01(txt) {
console.log(txt);
}

var v02 = function f02(txt) {
console.log(txt);
};
v02("2. expression 可省略func name");

(function f01(txt) {
console.log(txt);
})("3. 宣告時順便就呼叫");

若連續兩句法三的寫法中間要用分號隔開

使用 IIFE

// 不會污染 global
(function () {
var tmp = "hello";
console.log(tmp);
})();

// 會污染 global
var tmp = "hello";
console.log(tmp);
function f1(txt) {
console.log(txt);
}
f1("1. statement 另外呼叫 且必須有 func name");

var v2 = function f2(txt) {
console.log(txt);
};
v2("2. expression 另外呼叫");

var v3 = (function f3(txt) {
console.log(txt);
})("3. IIFE 直接呼叫");

var f4 = function f4(txt) {
return txt;
};
console.log(f4("4. expression 回傳值"));

var v5 = (function f5(txt) {
return txt;
})("5. IIFE 回傳值");
console.log(v5);

(function f6(txt) {
console.log(txt);
})("6. expression");

箭頭函式

https://developer.cdn.mozilla.net/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions

var a1 = (txt) => {
console.log(txt);
};
a1("1. 箭頭 另外呼叫");

var a2 = (txt) => {
return txt;
};
console.log(a2("2. 箭頭 回傳值"));
(參數1, 參數2,, 參數N) => { 陳述式; }

(參數1, 參數2,, 參數N) => 表示式;
// 等相同(參數1, 參數2, …, 參數N) => { return 表示式; }

// 只有一個參數時,括號才能不加:
(單一參數) => { 陳述式; }
單一參數 => { 陳述式; }

//若無參數,就一定要加括號:
() => { statements }

// 用大括號將內容括起來,返回一個物件字面值表示法:
params => ({foo: bar})

// 支援其餘參數與預設參數
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2,, paramN = defaultValueN) => {
statements }

// 也支援 parameter list 的解構
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f(); // 6

OO

function Person(name) {
this.name = name;
this.greeting = function () {
console.log("Hi! I'm " + this.name + ".");
};
}

var person1 = new Person("Bob");
var person2 = new Person("Sarah");

person1.greeting();
console.log(person1.name);
// Hi! I'm Bob.
// Bob

person2.greeting();
// Hi! I'm Sarah.

閉包

  • 可以把函數內部變數往外傳遞
  • 可以封裝私有 method
var v1 = function f1() {
var n = 999;
//閉包
return () => {
return n;
};
};
console.log(v1()()); //999
function plus_one(start) {
return () => {
return start++;
};
}
var inc = plus_one(5);
console.log(inc());
console.log(inc());

var inc = plus_one(5);
console.log(inc());
console.log(inc());
console.log(inc());

class 使用方式

class C {
constructor(name, age) {
this.name = name;
this.age = age;
}
hi() {
console.log(`hi im ${this.name}, ${this.age} years old.`);
}
}

var c = new C("cccc", 20);
c.hi();
function F(name, age) {
this.name = name;
this.age = age;
this.hi = function () {
console.log(`hi im ${this.name}, ${this.age} years old.`);
};
}

var f = new F("fffff", 20);
f.hi();

constructor

隨著 class 一同建立並初始化物件的特殊方法,一個 class 只能有一個稱為 constructor ,出現兩次以上就會發生 SyntaxError 錯誤,如果不指定建構子,就會使用預設的建構子。

預設的建構子長得像這樣:

constructor() {}

錯誤處理

function do2(txt) {
console.log(txt);
throw new Error("自訂錯誤訊息");
}

function do_something() {
try {
do2("1. try block內 -> error也會繼續執行");
} catch (e) {
console.log("2. try block 有error -> 才執行");
console.log("-> 印出error的追蹤");
console.log(e.stack);
} finally {
console.log("*此處一定會執行");
}
do2("3. try block外 -> 若error會停止程式");
console.log("4. 所以這裏執行不到");
}

do_something();
// 1. try block內 -> error也會繼續執行
// 2. try block 有error -> 才執行
// -> 印出error的追蹤
// Error: 自訂錯誤訊息
// at do2 (script.js:3)
// at do_something (script.js:8)
// at script.js:18
// *此處一定會執行
// 3. try block外 -> 若error會停止程式
// Uncaught Error: 自訂錯誤訊息
// at do2 (script.js:3)
// at do_something (script.js:14)
// at script.js:18
function f() {
try {
console.log(0);
throw 'bug';
} catch (e) {
console.log(1);
return 2; // 这句原本会延迟到 finally 代码块结束再执行
} finally {
console.log(3);
return 4; // 这句会覆盖掉 return 2
}
}

var result = f();
console.log(result)
// 若註解 return 4-> 0 1 3 2
// 若有 return 4-> 0 1 3 4

其他

顯示網頁上所有元素外框

[].forEach.call($$("*"),function(a){a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)})
1 == "1"; // true
1 === "1"; // false

expression 表達數值 statement 執行動作 object 物件

// 大括內先當作 statement or expression , 不能執行才被當作 object
{
1 + 2;
} // expression: 3
{
console.log(123);
} // statement: 123
{
foo: 123;
} // object: { foo: 123 }

// 多加一層小括會被當作 object
({ foo: 123 }); // object: {foo: 123}
// ({console.log(123)}) // SyntaxError
var o = {
6: 666,
hi: "hello",
};
console.log(o[3 + 3]); //666
console.log(o["h" + "i"]); //hello
console.log(o["6"]); //666

console.log(Object.keys(o)); //["6", "hi"]
console.log(Object.getOwnPropertyNames(o)); //["6", "hi"]
for (let k in o) {
console.log(`${k}: ${o[k]}`);
}
// 6: 666
// hi: hello
console.log("toString" in o); //true
console.log(o.hasOwnProperty("toString")); //false, 是ienumerable & 是繼承來的
console.log(o.toString); //ƒ toString() { [native code] } 或 [Function: toString]
console.log(o.toString()); //[object Object]
let word = "abc";
for (let i in word) {
console.log(i, word[i]);
}
// 0 a
// 1 b
// 2 c

可以在函數內部 call 自己

var pri = (function x() {
console.log(typeof x); //可以在函數內部call 自己
})(); //function

函數提升

var f = function () {
console.log("1");
};

function f() {
console.log("2");
}
f(); // 1

上面例子中, 表面上後面聲明的函數 f, 應該覆蓋前面的 var 賦值語句, 但是由於存在函數提升, 實際上正好反過來。

利用 void()使頁面不轉跳但是執行 func

<a href="javascript: void(document.form.submit())">按鈕</a>

自訂 type 顯示

var type = function (o) {
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1]; //.toLowerCase();
};
var types = [
type({}),
type([]),
type(5),
type(null),
type(),
type(/abcd/),
type(new Date()),
];
types.forEach((e) => {
console.log(e);
});
var v = 23;
console.log(typeof v);
// number

var v2 = new Number(23);
console.log(typeof v2);
// object

字串過濾

replace(/\D/g, "");
replace(/\W/g, "");

看 meta

document.querySelectorAll("meta").forEach((e) => {
console.log(e);
});

document.querySelectorAll("meta").forEach((e) => {
console.log(e.content);
});

get local storage by name

JSON.parse(decodeURIComponent(localStorage.getItem("name")));
JSON.parse(
decodeURIComponent(
((cookiename) => {
return document.cookie
.match(cookiename + "=[^;]+")[0]
.replace(/^[^=]+=/, "");
})("cart")
)
)[0]["commodity_id"];
document.cookie.split("; ").reduce((accum, x) => {
const kv = x.split("=");
return { ...accum, ...{ [kv[0]]: kv[1] } };
}, {});
JSON.parse(
decodeURIComponent(
document.cookie.split("; ").reduce((accum, x) => {
const kv = x.split("=");
return { ...accum, ...{ [kv[0]]: kv[1] } };
}, {})["cart"]
)
)[0]["commodity_id"];

取自己的文字但是不包含子元素的文字

document.querySelector("span.price").childNodes[0].textContent;
$("span.price").clone().children().remove().end().text();
  • document.querySelector().childNodes // 只 tag
  • document.querySelector().children // tag&所有可放 text 的地方

小括弧功能

只會存最後一個,但會執行每個

var s = 5;
var t = (1, 2, 3, 4, (s += 10), 6, 7);
console.log(s, t); //15 7

參考資料

https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Classes/constructor

https://realdennis.medium.com/javascript-%E8%81%8A%E8%81%8Acall-apply-bind%E7%9A%84%E5%B7%AE%E7%95%B0%E8%88%87%E7%9B%B8%E4%BC%BC%E4%B9%8B%E8%99%95-2f82a4b4dd66

https://hackmd.io/9UsfHCB2Rw2mAKTkXm6dWw#Number

https://ithelp.ithome.com.tw/articles/10228789

https://wangdoc.com/javascript/features/style.html

https://openhome.cc/Gossip/JavaGossip-V1/RegularExpression.htm

先看

https://wangdoc.com/javascript/stdlib/attributes.html